package com.wechatapp.mengyao.poetry.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.ElasticsearchException;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import com.fasterxml.jackson.core.type.TypeReference;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.wechatapp.mengyao.poetry.constants.PoetryCollection;
import com.wechatapp.mengyao.poetry.core.CommonConstants;
import com.wechatapp.mengyao.poetry.core.CurrentContext;
import com.wechatapp.mengyao.poetry.core.JsonUtils;
import com.wechatapp.mengyao.poetry.core.PageResult;
import com.wechatapp.mengyao.poetry.entity.ChinesePoetryPinyin;
import com.wechatapp.mengyao.poetry.mapper.ChinesePoetryPinyinMapper;
import com.wechatapp.mengyao.poetry.vo.ChinesePoetryContentView;
import com.wechatapp.mengyao.poetry.vo.ChinesePoetryView;
import com.wechatapp.mengyao.poetry.entity.ChinesePoetry;
import com.wechatapp.mengyao.poetry.entity.PoetryType;
import com.wechatapp.mengyao.poetry.exception.ElasticSearchException;
import com.wechatapp.mengyao.poetry.exception.ExceptionCode;
import com.wechatapp.mengyao.poetry.mapper.ChinesePoetryMapper;
import com.wechatapp.mengyao.poetry.mapper.FavoriteMapper;
import com.wechatapp.mengyao.poetry.mapper.PoetryTypeMapper;
import com.wechatapp.mengyao.poetry.service.ChinesePoetryAuthorService;
import com.wechatapp.mengyao.poetry.service.ChinesePoetryReciteFileService;
import com.wechatapp.mengyao.poetry.service.ChinesePoetryService;
import com.wechatapp.mengyao.poetry.vo.ChinesePoetryESView;
import com.wechatapp.mengyao.poetry.vo.ChinesePoetryPageView;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchPhaseExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import tk.mybatis.mapper.entity.Example;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author yanghuaxu
 * @date 2025/5/11 18:09
 */
@Service
@Slf4j
public class ChinesePoetryServiceImpl implements ChinesePoetryService {

    @Autowired
    private ChinesePoetryMapper poetryMapper;
    @Autowired
    private PoetryTypeMapper poetryTypeMapper;
    @Autowired
    private ElasticsearchClient elasticsearchClient;
    @Autowired
    private FavoriteMapper favoriteMapper;
    @Autowired
    private ChinesePoetryReciteFileService poetryReciteFileService;
    @Autowired
    private ChinesePoetryAuthorService poetryAuthorService;
    @Autowired
    private ChinesePoetryPinyinMapper chinesePoetryPinyinMapper;

    @Override
    public boolean saveOrUpdate(ChinesePoetry poetry) {
        ChinesePoetryView p = getById(poetry.getId());
        int r = 0;
        if (p != null) {
            r = poetryMapper.update(poetry);
        } else {
            r = poetryMapper.insert(poetry);
        }
        return r > 0;
    }

    @Override
    public boolean deleteById(Integer id) {
        return poetryMapper.deleteById(id) > 0;
    }

    @Override
    public ChinesePoetryView getById(Integer id) {
        ChinesePoetryView p = poetryMapper.selectById(id).convert();
        // 设置诗歌的拼音
        List<ChinesePoetryPinyin> poetryPinyinList = chinesePoetryPinyinMapper.getPoetryPinYinByPoemId(id);
        if (CollectionUtils.isEmpty(poetryPinyinList)) {
            p.setWithPinyin(false);
        } else {
            p.setWithPinyin(true);
        }
        // 第一个map是行索引，第二个map是字符索引
        Map<Integer, Map<Integer, ChinesePoetryPinyin>> indexMap = poetryPinyinList.stream()
                .collect(Collectors.groupingBy(ChinesePoetryPinyin::getLineIndex,
                        Collectors.toMap(ChinesePoetryPinyin::getCharIndex, pinyin -> pinyin, (k1, k2) -> k1)));

        List<String> poetryContent = JsonUtils.parseList(p.getContentString(), new TypeReference<List<String>>() {
        });
        List<ChinesePoetryContentView> poetryContentList = new ArrayList<>();
        // 遍历诗歌
        for (int i = 0; i < poetryContent.size(); i++) {
            Map<Integer, ChinesePoetryPinyin> charIndex = indexMap.getOrDefault(i, new HashMap<>());
            String poetry = poetryContent.get(i);
            for (int j = 0; j < poetry.length(); j++) {
                ChinesePoetryPinyin pinyin = charIndex.get(j);
                if (ObjectUtils.isEmpty(pinyin)) {
                    poetryContentList.add(new ChinesePoetryContentView(String.valueOf(poetry.charAt(j)), "", i));
                } else {
                    poetryContentList.add(new ChinesePoetryContentView(String.valueOf(poetry.charAt(j)), pinyin.getPinyin(), i));
                }
            }
        }
        p.setContent(poetryContentList);
        p.setFilePath(poetryReciteFileService.getFilePathById(Long.valueOf(id)));
        return p;
    }

    @Override
    public ChinesePoetryView getRandomById(Integer currentId) {
        ChinesePoetry p = poetryMapper.selectByPrimaryKey(currentId);
        int randomOffset = poetryMapper.getRandomOffset(currentId);
        Integer newPoemId = poetryMapper.getRandomPoetry(p.getCollection(), randomOffset);
        ChinesePoetryView result = getById(newPoemId);
        result.setFilePath(poetryReciteFileService.getFilePathById(Long.valueOf(result.getId())));
        return result.convert();
    }

    @Override
    public PageResult<ChinesePoetryPageView> listByPage(Map<String, String> map) {
        int pageNum = Integer.parseInt(map.getOrDefault("pageNum", "1"));
        int pageSize = Integer.parseInt(map.getOrDefault("pageSize", "10"));
        map.put("userId", CurrentContext.getCurrentOperatingUser().getUserId().toString());
        PageHelper.startPage(pageNum, pageSize, true);
        List<ChinesePoetryPageView> result = poetryMapper.selectByCondition(map);
        result.forEach(ChinesePoetryPageView::convert);
        return PageResult.fromPage((Page<ChinesePoetryPageView>) result, result);
    }

    @Override
    public List<PoetryType> getAllPoetryType() {
        Example example = new Example(PoetryType.class);
        example.orderBy("poetryOrder").asc();
        return poetryTypeMapper.selectByExample(example);
    }

    @Override
    public Map<Integer, String> getCollection(List<Integer> poetryIdList) {
        Example example = new Example(ChinesePoetry.class);
        example.createCriteria().andIn("id", poetryIdList);
        return poetryMapper.selectByExample(example).stream().collect(Collectors.toMap(ChinesePoetry::getId, v -> PoetryCollection.fromCode(v.getCollection())));
    }

    @Override
    public PageResult<ChinesePoetryPageView> listByPageFromEs(Map<String, String> map) {
        int pageNum = Integer.parseInt(map.getOrDefault("pageNum", "1"));
        int pageSize = Integer.parseInt(map.getOrDefault("pageSize", "10"));
        int from = (pageNum - 1) * pageSize;

        String userId = CurrentContext.getCurrentOperatingUser().getUserId().toString();

        Set<Integer> currentUserFavoritePoetrySet = new HashSet<>(favoriteMapper.getUserFavoritePoetry(userId));

        String author = map.get("author");
        String title = map.get("title");
        String content = map.get("content");
        String collection = map.get("collection");
        String queryString = map.get("queryString");
        String isFavorite = map.get("isFavorite");

        // 构建查询
        List<Query> mustQueries = new ArrayList<>();
        if (StringUtils.hasText(author)) {
            mustQueries.add(MatchPhraseQuery.of(m -> m.field("author").query(author))._toQuery());
        }
        if (StringUtils.hasText(title)) {
            mustQueries.add(MatchPhraseQuery.of(m -> m.field("title").query(title))._toQuery());
        }
        if (StringUtils.hasText(content)) {
            mustQueries.add(MatchQuery.of(m -> m.field("content").query(content))._toQuery());
        }
        if (StringUtils.hasText(collection)) {
            mustQueries.add(TermsQuery.of(t -> t
                    .field("collection.keyword")
                    .terms(terms -> terms.value(
                            Arrays.stream(collection.split(","))
                                    .map(FieldValue::of)
                                    .collect(Collectors.toList())))
            )._toQuery());
        }

        if (StringUtils.hasText(queryString)) {
            // 使用multi_match查询替代多个单字段查询，提高精度
            mustQueries.add(Query.of(q -> q
                    .multiMatch(mm -> mm
                            .query(queryString)
                            .fields("author", "title", "content")
                            .type(TextQueryType.BestFields) // 使用最佳匹配模式
                    )
            ));
        }

        // 最终 bool 查询
        BoolQuery.Builder mainBoolQuery = new BoolQuery.Builder();
        if (!mustQueries.isEmpty()) {
            mainBoolQuery.must(mustQueries);
        }

        // 构建收藏项查询 - 作为独立的should查询存在
        BoolQuery.Builder favoriteBoolQuery = new BoolQuery.Builder();
        if (!currentUserFavoritePoetrySet.isEmpty()) {
            favoriteBoolQuery.should(s -> s
                    .terms(t -> t
                            .field("id")
                            .terms(ts -> ts.value(currentUserFavoritePoetrySet.stream()
                                    .map(id -> FieldValue.of(id.toString()))
                                    .collect(Collectors.toList())))
                            .boost(2.0f)
                    ));
        }

        BoolQuery.Builder finalQuery = new BoolQuery.Builder()
                .must(mainBoolQuery.build()._toQuery()) // 必须满足主查询条件
                .should(favoriteBoolQuery.build()._toQuery()); // 满足收藏条件会提升评分

        // 查询执行
        SearchResponse<ChinesePoetryESView> response;
        try {
            response = elasticsearchClient.search(s -> s
                            .index("chinese_poetry")
                            .from(from)
                            .size(pageSize)
                            .query(q -> q.bool(finalQuery.build()))
                            // 是否展示全部数据
                            .trackTotalHits(t -> t.enabled(true))
                            .highlight(h -> h
                                    .fields("author", hf -> hf.preTags("<em>").postTags("</em>"))
                                    .fields("title", hf -> hf.preTags("<em>").postTags("</em>"))
                                    .fields("content", hf -> hf.preTags("<em>").postTags("</em>"))
                            )
                            .sort(Arrays.asList(
                                    SortOptions.of(so -> so.score(sc -> sc.order(SortOrder.Desc))),
                                    SortOptions.of(so -> so.field(f -> f.field("id").order(SortOrder.Asc)))
                            )),
                    ChinesePoetryESView.class
            );
        } catch (ElasticsearchException esEx) {
            // 根据 Elasticsearch 错误状态分类处理
            switch (esEx.status()) {
                case 400:
                    esEx.printStackTrace();
                    throw new ElasticSearchException(ExceptionCode.ELASTIC_SEARCH_EXCEPTION.getExceptionCode(),
                            "Elasticsearch 查询请求格式错误: " + esEx.getMessage());
                case 404:
                    throw new ElasticSearchException(ExceptionCode.ELASTIC_SEARCH_EXCEPTION.getExceptionCode(),
                            "Elasticsearch 查询未找到相关数据: " + esEx.getMessage());
                case 503:
                    throw new ElasticSearchException(ExceptionCode.ELASTIC_SEARCH_EXCEPTION.getExceptionCode(),
                            "Elasticsearch 服务不可用: " + esEx.getMessage());
                default:
                    throw new ElasticSearchException(ExceptionCode.ELASTIC_SEARCH_EXCEPTION.getExceptionCode(),
                            "Elasticsearch 查询失败: " + esEx.getMessage());
            }
        } catch (IOException ioEx) {
            // 网络连接问题，无法与 Elasticsearch 通信
            throw new ElasticSearchException(ExceptionCode.ELASTIC_SEARCH_EXCEPTION.getExceptionCode(),
                    "与 Elasticsearch 的连接失败: " + ioEx.getMessage());
        } catch (SearchPhaseExecutionException speEx) {
            // 查询执行阶段失败，通常是语法错误或配置问题
            throw new ElasticSearchException(ExceptionCode.ELASTIC_SEARCH_EXCEPTION.getExceptionCode(),
                    "Elasticsearch 查询执行失败: " + speEx.getMessage());
        } catch (Exception ex) {
            // 捕获所有其他异常
            throw new ElasticSearchException(ExceptionCode.ELASTIC_SEARCH_EXCEPTION.getExceptionCode(),
                    "未知的 Elasticsearch 查询错误: " + ex.getMessage());
        }

        List<ChinesePoetryPageView> list = response.hits().hits().stream()
                .map(hit -> {
                    ChinesePoetryESView source = hit.source();
                    if (source == null) {
                        return null;
                    }
                    ChinesePoetryPageView dto = source.convert2Page();
                    dto.setIsFavorite(currentUserFavoritePoetrySet.contains(dto.getId())
                            ? CommonConstants.BOOLEAN_TRUE : CommonConstants.BOOLEAN_FALSE);
                    dto.convert();
                    return dto;
                }).filter(Objects::nonNull)
                .collect(Collectors.toList());
        Map<Integer, Integer> authPoetryCount = poetryAuthorService.getAuthPoetryCount(list.stream().map(ChinesePoetryPageView::getAuthorId).collect(Collectors.toList()));
        Map<Integer, String> collectMap = getCollection(list.stream().map(ChinesePoetryPageView::getId).collect(Collectors.toList()));
        list.forEach(p -> {
            p.setPoetryCount(authPoetryCount.getOrDefault(p.getAuthorId(), 0));
            p.setCollection(collectMap.getOrDefault(p.getId(), ""));
        });

        // 收藏过滤
        if (CommonConstants.BOOLEAN_TRUE.equals(isFavorite)) {
            list = list.stream().filter(c -> CommonConstants.BOOLEAN_TRUE.equals(c.getIsFavorite())).collect(Collectors.toList());
        } else if (CommonConstants.BOOLEAN_FALSE.equals(isFavorite)) {
            list = list.stream().filter(c -> CommonConstants.BOOLEAN_FALSE.equals(c.getIsFavorite())).collect(Collectors.toList());
        }

        long total = response.hits().total().value();
        int pages = (int) Math.ceil((double) total / pageSize);

        return new PageResult<>(pageNum, pageSize, total, pages, list);
    }


}
